﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Outlook = Microsoft.WindowsMobile.PocketOutlook;
using GSync.Enums;
namespace GSync
{

    public class SyncManager : IDisposable
    {

        #region public methods

        public SyncManager()
        {

        }

        /// <summary>
        /// Executes syncronization between local and server data.
        /// </summary>
        public void Synchronize()
        {

            CheckStorageTypes();

            IEnumerable<InternalEntry> remoteEntries = RemoteStorage.AllRecords;
            IEnumerable<InternalEntry> locaEntries = LocalStorage.AllRecords;


            // copy new entries from remote storage to local
            CopyNewEntries(RemoteStorage, LocalStorage, remoteEntries);

            // copy new entries from local storage to remote
            CopyNewEntries(LocalStorage, RemoteStorage, locaEntries);


            UpdateLocalFromRemote(remoteEntries, locaEntries);

            UpdateRemoteFromLocal(remoteEntries, locaEntries);

            

        }

        private void UpdateRemoteFromLocal(IEnumerable<InternalEntry> remoteEntries, IEnumerable<InternalEntry> locaEntries)
        {
            List<InternalEntry> localUpdated = (from entry in locaEntries
                                                where entry.HasChanged && !entry.Processed && entry.CanSynchronize
                                                select entry).ToList();


            foreach (InternalEntry localEntry in localUpdated)
            {
                localEntry.Processed = true;
                InternalEntry remoteEntry = remoteEntries.FirstOrDefault(c => c.ID == localEntry.ID);


                //If remote entry was removed, do nothing.
                if (remoteEntry == null)
                    continue;

                remoteEntry.Processed = true;

                // All conflicts are already resolved. We just need to update server entries.
                UpdateEntry(localEntry, RemoteStorage);

            }
        }

        private void UpdateLocalFromRemote(IEnumerable<InternalEntry> remoteEntries, IEnumerable<InternalEntry> locaEntries)
        {
            List<InternalEntry> remoteUpdated = (from entry in remoteEntries
                                                 where entry.HasChanged && !entry.Processed && entry.CanSynchronize
                                                 select entry).ToList();


            foreach (InternalEntry remoteEntry in remoteUpdated)
            {

                remoteEntry.Processed = true;
                InternalEntry localEntry = locaEntries.FirstOrDefault(c => c.ID == remoteEntry.ID);


                //If local entry was removed. Do nothing.
                if (localEntry == null)
                {
                    //ResolveRemovalConflict(
                    //RemoteStorage.DeleteEntry( up
                    // todo: add removal logic
                    continue;

                }


                localEntry.Processed = true;


                if (!localEntry.HasChanged)
                    UpdateEntry(remoteEntry, LocalStorage);
                else
                    ResolveUpdateConflict(remoteEntry, localEntry);


            }
        }

        private void UpdateEntry(InternalEntry srcEntry, IDataProvider tgtStorage)
        {
            tgtStorage.UpdateEntry(srcEntry);
            CacheMngr[srcEntry.ID].HashCode = srcEntry.GetHashCode();
        }

        private void ResolveUpdateConflict(InternalEntry remoteEntry, InternalEntry localEntry)
        {
            ConflictResolver.LocalEntry = localEntry;
            ConflictResolver.RemoteEntry = remoteEntry;

            switch (ConflictResolver.ResolveConflict())
            {
                case UpdatePolicy.UseServerData:
                    if (remoteEntry.CanSynchronize)
                        UpdateEntry(remoteEntry, LocalStorage);
                    break;
                case UpdatePolicy.UseLocalData:
                    if (localEntry.CanSynchronize)
                        UpdateEntry(localEntry, RemoteStorage);
                    break;
                case UpdatePolicy.Cancel:
                    break;
                default:
                    break;
            }
        }


        protected void CopyNewEntries(IDataProvider srcProvider, IDataProvider tgtProvider, IEnumerable<InternalEntry> fullSrcEntryList)
        {
            var newEntries = from entry in fullSrcEntryList
                             where entry.IsNew && entry.CanSynchronize
                             select entry;

            // copy new records from remote storage to local
            foreach (InternalEntry newEntry in newEntries)
            {

                tgtProvider.AddEntry(newEntry);

                // If ID is assigned automatically by storage, update it in another storage.
                if (!tgtProvider.CanModifyEntryID)
                {
                    srcProvider.UpdateEntry(newEntry);
                }

                CacheMngr[newEntry.ID] = new CacheRecord()
                                                {
                                                    HashCode = newEntry.GetHashCode(),
                                                    ID = newEntry.ID,
                                                    Type = newEntry.GetType().ToString(),
                                                    Updated = DateTime.Now
                                                };

                newEntry.Processed = true;

            }
        }
        #endregion

        public IDataProvider RemoteStorage { get; set; }
        public IDataProvider LocalStorage { get; set; }
        public IHashCodesCacheManager CacheMngr { get; set; }
        public IConflictResolver ConflictResolver { get; set; }

        #region Protected and private methods


        public void CheckStorageTypes()
        {
            if (!RemoteStorage.CanModifyEntryID && !LocalStorage.CanModifyEntryID)
            {
                throw new ApplicationException("Unable synchronize storages if both of them can't modify entry ID.");
            }
        }




        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            CacheMngr.Save();


            if (RemoteStorage != null)
                RemoteStorage.Dispose();

            if (LocalStorage != null)
                LocalStorage.Dispose();



        }

        #endregion
    }

}
